package my.graduation.service.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import my.graduation.constant.RegScheStatusConstant;
import my.graduation.dto.DocRegScheDto;
import my.graduation.dto.PatRegScheDto;
import my.graduation.entity.*;
import my.graduation.enums.HttpCodeEnums;
import my.graduation.mapper.*;
import my.graduation.result.Result;
import my.graduation.service.DoctorService;
import my.graduation.service.RegistrationScheduleService;
import my.graduation.util.DateCalculator;
import my.graduation.vo.DoctorRegVO;
import my.graduation.vo.DoctorVO;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.time.LocalDate;

import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: RavenLyn
 * @Date: 2024-12-13
 */
@Service
public class RegistrationScheduleServiceImpl extends ServiceImpl<RegistrationScheduleMapper, RegistrationSchedule> implements RegistrationScheduleService {

    @Autowired
    private DoctorMapper doctorMapper;

    @Autowired
    private ClinicMapper clinicMapper;

    @Autowired
    private RegistrationScheduleMapper registrationScheduleMapper;

    @Autowired
    private ExaminationScheduleMapper examinationScheduleMapper;

    @Autowired
    private RegistrationModifyMapper registrationModifyMapper;

    /**
     * 挂号排班生成
     */
    @Override
    @Transactional
    public Result generate() {
        // 获取下周一和下周六的日期
        LocalDate nextMonday = DateCalculator.calculateNextMondayDate();
        LocalDate nextSaturday = DateCalculator.calculateNextSaturdayDate();

        // 获取clinicIdList
        List<Integer> clinicIdList = clinicMapper.getIdList();

        try{
            for(Integer clinicId : clinicIdList){
                // 获取当前门诊下的所有医生
                List<Doctor> allDoctorList = doctorMapper.getDoctorByClinicId(clinicId);

                // 排工作日的班
                for (Doctor doctor : allDoctorList) {
                    // 获取该医生已有的ExaminationSchedule,且是在nextMonday之后，status!=3的记录
                    List<ExaminationSchedule> existedSche = examinationScheduleMapper
                            .getExistedExamSche(doctor.getId(), Date.valueOf(nextMonday));

                    // 使用Set存储已有Reg排班的日期和班次组合
                    Set<String> existedDateShiftCombos = new HashSet<>();
                    for (ExaminationSchedule schedule : existedSche) {
                        LocalDate date = schedule.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // 假设 schedule.getDate() 返回的是 Date 类型
                        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                        String formattedDate = date.format(formatter);
                        existedDateShiftCombos.add(formattedDate + "_" + schedule.getShift());
//                        System.out.println("exam中已有的排班" + formattedDate + "_" + schedule.getShift());
                    }


                    int workdays = getWorkdaysByTitle(doctor.getTitle());
                    // 使用set来确保同一个医生不会出现"同日同班次"的排班
                    Set<String> assignedDateShiftCombos = new HashSet<>();
                    Random random = new Random();

                    // 创建一个周一到周五的日期列表
                    List<LocalDate> workdaysList = new ArrayList<>();
                    for (int i = 0; i < 5; i++) {
                        workdaysList.add(nextMonday.plusDays(i));
                    }

                    // 随机分配工作日班次
                    for (int i = 0; i < workdays; i++) {
                        LocalDate date;
                        int shift;
                        do {
                            // 随机选择一个日期
                            int randomIndex = random.nextInt(workdaysList.size());
                            date = workdaysList.get(randomIndex);
                            shift = random.nextInt(2);
                        } while (assignedDateShiftCombos.contains(date + "_" + shift)||
                                existedDateShiftCombos.contains(date + "_" + shift)); // 检查是否已经为特定日期和班次分配了排班

                        String dateShiftCombo = date + "_" + shift;
//                        System.out.println("本次生成与插入："+dateShiftCombo);
                        assignedDateShiftCombos.add(dateShiftCombo);
                        int dayOfWeek = DateCalculator.getDayOfWeekAsInt(date);

                        RegistrationSchedule newOne = new RegistrationSchedule();
                        newOne.setDepartmentId(doctor.getDepartmentId());
                        newOne.setClinicId(doctor.getClinicId());
                        newOne.setDoctorId(doctor.getId());
                        newOne.setDoctorName(doctor.getName());
                        newOne.setDate(java.sql.Date.valueOf(date));
                        newOne.setDayOfWeek(dayOfWeek);
                        newOne.setShift(shift);
                        newOne.setStatus(RegScheStatusConstant.UNCONFIRMED);
                        save(newOne);
                    }
                }

                // 获取title为0的doctorList0排周末日的班
                List<Doctor> doctorList0 = allDoctorList.stream()
                        .filter(doctor -> doctor.getTitle() == 0)
                        .collect(Collectors.toList());
                for (Doctor doctor : doctorList0) {

                    // 获取该医生已有的ExaminationSchedule,且是在nextSaturday之后，status!=3的记录
                    List<ExaminationSchedule> existedSche = examinationScheduleMapper
                            .getExistedExamSche(doctor.getId(), Date.valueOf(nextSaturday));

                    // 使用Set存储已有Reg排班的日期和班次组合
                    Set<String> existedDateShiftCombos = new HashSet<>();
                    for (ExaminationSchedule schedule : existedSche) {
                        existedDateShiftCombos.add(schedule.getDate() + "_" + schedule.getShift());
                    }
                    // 随机分配周末的班次（周六或周日）
                    boolean isAssigned = false;
                    while (!isAssigned) {
                        LocalDate date = nextSaturday.plusDays(new Random().nextInt(2) == 0 ? 0 : 1);
                        int shift = new Random().nextInt(2);
                        String dateShiftCombo = date + "_" + shift;
                        // 检查是否与已有排班冲突
                        if (!existedDateShiftCombos.contains(dateShiftCombo)) {
                            RegistrationSchedule newOne = new RegistrationSchedule();
                            newOne.setDepartmentId(doctor.getDepartmentId());
                            newOne.setClinicId(doctor.getClinicId());
                            newOne.setDoctorId(doctor.getId());
                            newOne.setDoctorName(doctor.getName());
                            newOne.setDate(java.sql.Date.valueOf(date));
                            newOne.setDayOfWeek(DateCalculator.getDayOfWeekAsInt(date));
                            newOne.setShift(shift);
                            newOne.setStatus(RegScheStatusConstant.UNCONFIRMED);
                            save(newOne);

                            isAssigned = true; // 标记已分配
                        }
                    }

                }
            }
            return Result.success();
        }catch (Exception e){
            e.printStackTrace();
            return Result.error(HttpCodeEnums.SYSTEM_ERROR);
        }
    }

    /**
     * 根据医生title安排班次
     * @param title
     * @return
     */
    private int getWorkdaysByTitle(int title) {
        switch (title) {
            case 0: return 5;
            case 1: return 4;
            case 2: return 3;
            case 3: return 2;
            default: return 0;
        }
    }

    /**
     * 管理员获取全部挂号排班信息
     * @return
     */
    @Override
    public List<RegistrationSchedule> adminGetList() {
        // 获取下周一和下周六的日期
        LocalDate nextMonday = DateCalculator.calculateNextMondayDate();
        LocalDate nextSaturday = DateCalculator.calculateNextSaturdayDate();

        return registrationScheduleMapper.adminGetListByOrder();
    }

    /**
     * 医生根据clinicId获取门诊排班表
     * @param dto
     * @return
     */
    @Override
    public Result<List<RegistrationSchedule>> doctorGetList(DocRegScheDto dto) {
        if(dto.getClinicId() != null){
            int clinicId = dto.getClinicId();
            List<RegistrationSchedule> list = registrationScheduleMapper.doctorGetListByOrder(clinicId);
            return Result.success(list);
        }else {
            return Result.error(HttpCodeEnums.SYSTEM_ERROR);
        }
    }



    /**
     * 医生修改挂号排班申请
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public Result doctorUpdate(DocRegScheDto dto) {
        // id doctorId dayOfWeek shift date moDay moDate moShift
        if(dto.getId() != null &&
                dto.getDoctorId() != null &&
                dto.getMoDate() != null &&
                dto.getMoDay() != null &&
                dto.getMoShift() != null &&
                dto.getDepartmentId() != null &&
                dto.getClinicId() != null &&
                StringUtils.isNotBlank(dto.getDoctorName())){

            // 1 去registration_schedule和examination_schedule查看是否有doctorId + moDate + moShift 同样的行
//            System.out.println("doctorId:"+dto.getDoctorId() + " moDate:"+dto.getMoDate() +" moShift:"+dto.getMoShift());
            List<RegistrationSchedule> regCheck = registrationScheduleMapper.getCheckList(dto.getDoctorId(), dto.getMoDate(), dto.getMoShift());
            List<ExaminationSchedule> examCheck = examinationScheduleMapper.getCheckList(dto.getDoctorId(), dto.getMoDate(), dto.getMoShift());


            if(regCheck.isEmpty() && examCheck.isEmpty()){
                // 2 无 - 返回200 - 可以向管理员发出修改申请。
                // 修改status=1 新增挂号修改申请表数据。
                update(Wrappers.<RegistrationSchedule>lambdaUpdate()
                        .eq(RegistrationSchedule::getId, dto.getId())
                        .set(RegistrationSchedule::getStatus, RegScheStatusConstant.MODIFYING));
                RegistrationModify modifyOne = new RegistrationModify();
                BeanUtils.copyProperties(dto,modifyOne);
                modifyOne.setId(null);
                modifyOne.setRegScheId(dto.getId());
                registrationModifyMapper.insert(modifyOne);
                return Result.success();
            }else {
                // 3 有 - 返回错误代码1012 - 返回该排班时间有冲突提示
                return Result.error(HttpCodeEnums.SCHEDULE_CONFLICT);
            }
        }else {
            return Result.error(HttpCodeEnums.SYSTEM_ERROR);
        }
    }

    @Autowired
    private RegistrationSpotsMapper registrationSpotsMapper;

    /**
     * 医生确认排班信息
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public Result doctorConfirm(DocRegScheDto dto) {
        // idList??
        if(dto.getIdList().length != 0 &&
                dto.getTitle() != null &&
                dto.getDoctorId() != null){
            List<RegistrationSchedule> regList = new ArrayList<>();
            for(Integer id : dto.getIdList()){
                RegistrationSchedule newOne = new RegistrationSchedule();
                newOne.setId(id);
                newOne.setStatus(RegScheStatusConstant.CONFIRMED);
                regList.add(newOne);
            }
            updateBatchById(regList);//这里为什么不会将其他字段赋为null？

            //存 regScheId,doctorId,timeSlot,remainSpots 进 registration_spots,需要获取 title -> remainSpots , shift -> timeSlot
            int spots = getRemainSpotsByTitle(dto.getTitle());
            for(Integer id : dto.getIdList()){ //循环这个医生的每一条排班 （每一条排班都有自己的shift)
                int shift = registrationScheduleMapper.getShift(id);

                if(shift == 0){//上午
                    for(int i = 0;i<6;i++){
                        RegistrationSpots newOne = new RegistrationSpots();
                        newOne.setRegScheId(id);
                        newOne.setDoctorId(dto.getDoctorId());
                        newOne.setRemainSpots(spots);
                        newOne.setTimeSlot(i);
                        registrationSpotsMapper.insert(newOne);
                    }
                }
                if(shift == 1){//下午
                    for(int i = 6;i<12;i++){
                        RegistrationSpots newOne = new RegistrationSpots();
                        newOne.setRegScheId(id);
                        newOne.setDoctorId(dto.getDoctorId());
                        newOne.setRemainSpots(spots);
                        newOne.setTimeSlot(i);
                        registrationSpotsMapper.insert(newOne);
                    }
                }
            }

            return Result.success();
        }else{
            return Result.error(HttpCodeEnums.SYSTEM_ERROR);
        }
    }


    /**
     * 根据医生title安排remainSpots
     * @param title
     * @return
     */
    private int getRemainSpotsByTitle(int title) {
        switch (title) {
            case 0: return 6;
            case 1: return 5;
            case 2: return 4;
            case 3: return 3;
            default: return 0;
        }
    }

    /**
     * 根据日期获取该门诊下的医生排班-进一步获取出医生信息，返回一个doctorRegVO
     * @param dto
     * @return
     */
    @Override
    public Result<List<DoctorRegVO>> patientGetRegScheList(PatRegScheDto dto) {
        // 1 参数校验 date clinicId
        if(dto.getDate() != null && dto.getClinicId() != null){
            // 2 获取出该门诊当天status=2的排班列表
            List<RegistrationSchedule> scheList = list(Wrappers.<RegistrationSchedule>lambdaQuery()
                    .eq(RegistrationSchedule::getClinicId, dto.getClinicId())
                    .eq(RegistrationSchedule::getDate, dto.getDate())
                    .eq(RegistrationSchedule::getStatus, RegScheStatusConstant.CONFIRMED));

            // 2.1 scheList 长度为0
            if(scheList.isEmpty()){
                return Result.success();
            }

            // 3 从排班列表中获取出doctorIdList
            List<Integer> doctorIdList = scheList.stream().map(RegistrationSchedule::getDoctorId).collect(Collectors.toList());

            // 4 封装成map，提高查询性能
//            Map<Integer, List<Integer>> doctorShiftMap = scheList.stream()
//                    .collect(Collectors.groupingBy(RegistrationSchedule::getDoctorId, Collectors.mapping(RegistrationSchedule::getShift, Collectors.toList())));
            Map<Integer,Integer> regScheIdShiftMap = scheList.stream()
                    .collect(Collectors.toMap(RegistrationSchedule::getId,RegistrationSchedule::getShift));
            Map<Integer,List<Integer>> doctorRegScheIdMap = scheList.stream()
                    .collect(Collectors.groupingBy(RegistrationSchedule::getDoctorId, Collectors.mapping(RegistrationSchedule::getId, Collectors.toList())));

            // 5 用doctorIdList去doctor表中获取所有医生数据
            List<Doctor> doctors = doctorMapper.selectBatchIds(doctorIdList);

            // 6 封装成DoctorRegVOList
            List<DoctorRegVO> doctorRegVoList = new ArrayList<>();
            for(Doctor doctor : doctors){
//                List<Integer> shiftList = doctorShiftMap.getOrDefault(doctor.getId(), Collections.emptyList());
                List<Integer> regScheIdList = doctorRegScheIdMap.getOrDefault(doctor.getId(), Collections.emptyList());
                for(Integer regScheId : regScheIdList){
                    Integer shift = regScheIdShiftMap.get(regScheId);
//                    System.out.println("regScheId:"+regScheId+" shift:"+shift);

                    doctorRegVoList.add(new DoctorRegVO(doctor,shift,regScheId));
                }
            }
            return Result.success(doctorRegVoList);

        }else {
            return Result.error(HttpCodeEnums.SYSTEM_ERROR);
        }
    }

    @Autowired
    private RegistrationSpotsMapper regSpotsMapper;

    /**
     * 定时任务，每日早上六点整触发，修改过期排班状态为 3
     */
    @Override
    public void handleExpired() {
        update(Wrappers.<RegistrationSchedule>lambdaUpdate()
                .le(RegistrationSchedule::getDate, LocalDate.now())
                .set(RegistrationSchedule::getStatus, RegScheStatusConstant.EXPIRED));
        List<RegistrationSchedule> list = list(Wrappers.<RegistrationSchedule>lambdaQuery()
                .eq(RegistrationSchedule::getStatus, RegScheStatusConstant.EXPIRED));
        // 从查询结果中提取 id 列表
        List<Integer> idList = list.stream()
                .map(RegistrationSchedule::getId)
                .collect(Collectors.toList());
        regSpotsMapper.delete(Wrappers.<RegistrationSpots>lambdaQuery()
                .in(RegistrationSpots::getRegScheId,idList));
    }
}
